package com.hx.vending.pay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hx.vending.bean.config.Constants;
import com.hx.vending.util.Constant;
import com.hx.vending.util.WXPayXmlUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.hibernate.validator.internal.util.privilegedactions.GetResource;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by Administrator on 2018/12/3.
 * 微信支付工具类
 */
public class WXPayUtil {
    private static Logger logger = Logger.getLogger(WXPayUtil.class);

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();


    /*
    * 获取公众号的token
    * */
    public static String getToken(){
        //-------因为访问次数的原因 ，后期加入缓存------------------
        //access_token
        URL postUrl = null;
        try {
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + Constant.APPID + "&secret=" + Constant.AppSecret + "";
            URL u = new URL(url);
            HttpsURLConnection huconn = (HttpsURLConnection) u.openConnection();
            BufferedReader in = null;
            StringBuilder result = new StringBuilder();
            huconn.connect();
            in = new BufferedReader(new InputStreamReader(huconn.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            if (in != null) {
                in.close();
            }
            // 打印返回结果
            Object json01 = JSONObject.parse(result.toString());
            logger.error("------------------打印结果是:" + json01 + "-----------------------------------------");
            Map jsonmap = (Map) json01;
            String token = (String) jsonmap.get("access_token");
            logger.error("获取token----" + token);
            return token;
        }catch(Exception e){
            e.printStackTrace();

        }
        return null;
    }

    /**
     * 获取公众号Ticket
     *
     * @return
     */
    public static String getTicket(String token){
        URL postUrl = null;
        try {
            String url2 = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token + "&type=jsapi";
            URL u = new URL(url2);
            HttpsURLConnection huconn = (HttpsURLConnection) u.openConnection();
            BufferedReader in = null;
            StringBuilder result = new StringBuilder();
            huconn.connect();
            in = new BufferedReader(new InputStreamReader(huconn.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            if (in != null) {
                in.close();
            }
            // 打印返回结果
            Object json01 = JSONObject.parse(result.toString());
            logger.error("------------------打印结果是:" + json01 + "-----------------------------------------");
            Map jsonmap = (Map) json01;
            String ticket2 = (String) jsonmap.get("ticket");
            logger.error("获取ticket：" + ticket2);
            return ticket2;
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * js接口签名校验
     *
     * @param jsapi_ticket
     * @param timestamp
     * @param noncestr
     * @param url
     * @return
     * @throws Exception
     */
    public static String jssdkSignature(String jsapi_ticket, String timestamp, String noncestr, String url) {
        jsapi_ticket = "jsapi_ticket=" + jsapi_ticket;
        noncestr = "noncestr=" + noncestr;
        timestamp = "timestamp=" + timestamp;
        url = "url=" + url;
        String arrys[] = new String[]{jsapi_ticket, noncestr, timestamp, url};
        Arrays.asList(arrys);
        StringBuffer buffer = new StringBuffer();
        for(int i = 0;i < arrys.length;i++){
            buffer.append(arrys[i]);
            if(i != arrys.length-1){
                buffer.append("&");
            }
        }
        return shal(buffer.toString());
    }

    public static String shal(String str){
        StringBuffer buffer = null;
        try {
            buffer = new StringBuffer();
            MessageDigest messageDigest = MessageDigest.getInstance("sha1");
            messageDigest.update(str.getBytes());
            for(byte bs:messageDigest.digest()){
                buffer.append(String.format("%02x",bs));
            }
            return buffer.toString();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }




    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr(Integer number) {
        char[] nonceChars = new char[number];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, Constants.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(Constant.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空，则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        //在最后拼接上key
        sb.append("key=").append(key);
        if (Constants.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (Constants.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }


    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data,String sign) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        //sign在最后添加
        org.w3c.dom.Element filed = document.createElement("sign");
        filed.appendChild(document.createTextNode(sign));
        root.appendChild(filed);

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }

    String resource =this.getClass().getClassLoader().getResource("/").getPath();
    /**
     * 请求，只请求一次，不做重试
     * @param urlSuffix
     * @param uuid
     * @param data
     * @param connectTimeoutMs
     * @param readTimeoutMs
     * @param useCert
     * @return
     * @throws Exception
     */
    public static String requestOnce(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, Boolean useCert,String path) throws Exception {
        BasicHttpClientConnectionManager connManager;

        if (useCert && null != path) {
            // 证书
            String certPath = path+"conf/apiclient_cert.p12";
            File file = new File(certPath);
            InputStream Stream = new FileInputStream(file);
            byte[] certData = new byte[(int) file.length()];
            Stream.read(certData);
            Stream.close();
            ByteArrayInputStream certBis = new ByteArrayInputStream(certData);
            char[] password = Constant.WX_MCH_ID.toCharArray();
            InputStream certStream = certBis;
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certStream, password);

            // 实例化密钥库 & 初始化密钥工厂
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, password);

            // 创建 SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());

            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    sslContext,
                    new String[]{"TLSv1"},
                    null,
                    new DefaultHostnameVerifier());

            connManager = new BasicHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslConnectionSocketFactory)
                            .build(),
                    null,
                    null,
                    null
            );
        }
        else {
            connManager = new BasicHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
                            .build(),
                    null,
                    null,
                    null
            );
        }


//        if (null != path) {
//
//            String certPath = path;
//            System.out.println(certPath);
//            File file = new File(certPath);
//            InputStream certStream = new FileInputStream(file);
//            byte[] certData = new byte[(int) file.length()];
//            certStream.read(certData);
//            certStream.close();
//            ByteArrayInputStream certBis = new ByteArrayInputStream(certData);
//
//            // 证书
//            char[] password = "1509042701".toCharArray();
//            InputStream certStrea = certBis;
//            KeyStore ks = KeyStore.getInstance("PKCS12");
//            ks.load(certStrea, password);
//
//            // 实例化密钥库 & 初始化密钥工厂
//            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
//            kmf.init(ks, password);
//
//            // 创建 SSLContext
//            SSLContext sslContext = SSLContext.getInstance("TLS");
//            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
//
//            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
//                    sslContext,
//                    new String[]{"TLSv1"},
//                    null,
//                    new DefaultHostnameVerifier());
//
//            connManager = new BasicHttpClientConnectionManager(
//                    RegistryBuilder.<ConnectionSocketFactory>create()
//                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
//                            .register("https", sslConnectionSocketFactory)
//                            .build(),
//                    null,
//                    null,
//                    null
//            );
//        }
//        else {
//            connManager = new BasicHttpClientConnectionManager(
//                    RegistryBuilder.<ConnectionSocketFactory>create()
//                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
//                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
//                            .build(),
//                    null,
//                    null,
//                    null
//            );
//        }

        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();

        String url = urlSuffix;
        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(data, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.addHeader("User-Agent", Constant.USER_AGENT + " " + Constant.WX_MCH_ID);
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity, "UTF-8");

    }


    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            com.hx.vending.pay.WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }


    /**
     * 日志
     * @return
     */
    public static org.slf4j.Logger getLogger() {
        org.slf4j.Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳，单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳，单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }



    /**
     * 发送连接请求
     * @param requestUrl 请求url
     * @param dataMap 请求体数据
     * @param requestMethod	请求的方法 POST/GET
     * @return
     */
    public static Map<String, Object> SendRequest(String requestUrl, Map dataMap, String requestMethod) {
        Map<String, Object> resultMap = new HashMap<>();
        //发送请求
        OutputStreamWriter out = null;
        InputStream is = null;
        URL url;

        try {
            url = new URL(requestUrl);// 创建连接
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod(requestMethod); // 设置请求方式

            String s = null;

            if (requestMethod.equals("POST")){
                connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
                connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
                s = JSON.toJSON(dataMap).toString();
                System.out.println("0000");
            }
            connection.connect();
            out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码
            out.append(s);
            out.flush();
            out.close();

            // 读取响应
            is = connection.getInputStream();
            int length = (int) connection.getContentLength();// 获取长度
            if (length != -1) {
                byte[] data = new byte[length];
                byte[] temp = new byte[512];
                int readLen = 0;
                int destPos = 0;
                while ((readLen = is.read(temp)) > 0) {
                    System.arraycopy(temp, 0, data, destPos, readLen);
                    destPos += readLen;
                }
                String result = new String(data, "UTF-8"); // utf-8编码
                resultMap = (Map<String, Object>) JSONObject.parse(result.toString());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != is && null != out){
                    out.close();
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultMap;
    }


    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, Constants.SignType signType) throws Exception {
        if (!data.containsKey("sign") ) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key, signType).equals(sign);
    }


    /**
     * 生成商家订单号
     * @return
     */
    public static String onlineOrderMerchantNumber(){

        Long time = getCurrentTimestampMs();//毫秒

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
        Date date = new Date();

        String year = sdf.format(date);

        //String fourNumber = generateNonceStr(4);

        //年份+时间戳
        //String orderNumber = year + time.toString().substring(0, 7) + fourNumber + time.toString().substring(7);
        String orderNumber = year + time.toString();

        return orderNumber;
    }

}
